using System;
using System.IO;
using System.Runtime.CompilerServices;

namespace lucere.io.concurrency
{
	/// <summary>
	///Implements <see cref="LockFactory"></see> using native OS file
	///locks.
	/// <p>The primary benefit of <see cref="NativeFSLockFactory"></see> is
	/// that lock files will be properly removed (by the OS) if
	/// the CLR has an abnormal exit.</p>
	/// 
	/// <p>Note that, unlike <see cref="SimpleFSLockFactory"></see>, the existence of
	/// leftover lock files in the filesystem on exiting the CLR
	/// is fine because the OS will free the locks held against
	/// these files even though the files still remain.</p>
	///
	/// <p>If you suspect that this or any other LockFactory is
	/// not working properly in your environment, you can easily
	/// test it by using <see cref="VerifyingLockFactory"></see>, <see cref="ILockVerifyServer"></see>
	/// and <see cref="LockStressTest"></see>.</p>
	///</summary>
	public class NativeFSLockFactory : LockFactory, INativeFSLockFactory
	{
		private DirectoryInfo _lockDir;
		private bool _tested;

		/// <summary>
		/// Create a NativeFSLockFactory instance, with null (unset)
		/// lock directory. When you pass this factory to a {@link FSDirectory}
		/// subclass, the lock directory is automatically set to the
		/// directory itsself. Be sure to create one instance for each directory
		/// your create!
		/// </summary>
		public NativeFSLockFactory() : this((DirectoryInfo) null)
		{
		}

		/// <summary>
		/// Create a NativeFSLockFactory instance, storing lock
		/// files into the specified lockDirName:
		/// </summary>
		/// <param name="lockDirName">lockDirName where lock files are created.</param>
		public NativeFSLockFactory(string lockDirName)
			: this(new DirectoryInfo(lockDirName))
		{
		}

		/// <summary>
		///Create a NativeFSLockFactory instance, storing lock
		/// files into the specified lockDir:
		///</summary>
		/// <param name="lockDir">lockDirName where lock files are created.</param>
		public NativeFSLockFactory(DirectoryInfo lockDir)
		{
			SetLockDir(lockDir);
		}

		private void SetLockDir(DirectoryInfo lockDir)
		{
			if (_lockDir != null)
				throw new InvalidOperationException("You can set the lock directory for this factory only once.");
			_lockDir = lockDir;
		}

		///<summary>
		///Return a new Lock instance identified by lockName.
		///<param name="lockName">name of the lock to be created.</param>
		///</summary>
		[MethodImpl(MethodImplOptions.Synchronized)]
		public override ILock MakeLock(string lockName)
		{
			//AcquireTestLock();
			if (_lockPrefix != null)
				lockName = _lockPrefix + "-" + lockName;
			return new NativeFSLock(_lockDir, lockName);
		}

		private void AcquireTestLock()
		{
			lock (this)
			{
				if (_tested)
					return;
				_tested = true;

				// Ensure that lockDir exists and is a directory.
				bool tmpBool;
				if (File.Exists(_lockDir.FullName))
					tmpBool = true;
				else
					tmpBool = Directory.Exists(_lockDir.FullName);
				if (!tmpBool)
				{
					try
					{
						Directory.CreateDirectory(_lockDir.FullName);
					}
					catch
					{
						throw new SystemException("Cannot create directory: " + _lockDir.FullName);
					}
				}
				else if (!Directory.Exists(_lockDir.FullName))
				{
					throw new SystemException("Found regular file where directory expected: " + _lockDir.FullName);
				}

				String randomLockName = "lucene-" + Convert.ToString(new Random().Next(), 16) + "-test.lock";

				ILock l = MakeLock(randomLockName);
				try
				{
					l.Obtain();
					l.Release();
				}
				catch (IOException e)
				{
					var e2 =
						new SystemException(
							"Failed to acquire random test lock; please verify filesystem for lock directory '" + _lockDir +
							"' supports locking", e);
					throw e2;
				}
			}
		}

		///<summary>
		/// Attempt to clear (forcefully unlock and remove) the
		/// specified lock.  Only call this at a time when you are
		/// certain this lock is no longer in use.
		/// <param name="lockName">name of the lock to be cleared.</param>
		/// </summary>
		public override void ClearLock(string lockName)
		{
			// Note that this isn't strictly required anymore
			// because the existence of these files does not mean
			// they are locked, but, still do this in case people
			// really want to see the files go away:
			if (_lockDir.Exists)
			{
				if (_lockPrefix != null)
				{
					lockName = _lockPrefix + "-" + lockName;
				}
				var lockFile = new FileInfo(Path.Combine(_lockDir.FullName, lockName));
				if (lockFile.Exists)
				{
					try
					{
						lockFile.Delete();
					}
					catch (IOException e)
					{
						throw new IOException("Cannot delete " + lockFile, e);
					}
				}
			}
		}

		/// <summary>
		/// Set the lock directory. This method can be only called
		/// once to initialize the lock directory. It is used by {@link FSDirectory}
		/// to set the lock directory to itsself.
		/// Subclasses can also use this method to set the directory
		/// in the constructor.
		/// </summary>
		public DirectoryInfo LockDirectory
		{
			get
			{
				return _lockDir;
			}
			set
			{
				SetLockDir(value);
			}
		}
	}
}